<?php
namespace WDB\Wrapper;
use WDB,
    WDB\Exception,
    WDB\Query\Element;

/**
 * Column representing database foreign key connected table.
 * 
 * @author Richard Ejem <richard(at)ejem.cz>
 * @package WDB
 * @todo remove MultiValue usages
 */
class ColumnForeign extends AbstractColumn implements iDBColumn, WDB\iSelectOptions, iOrderable, iFilterable
{   
    protected $webUIClass = '\\WDB\\WebUI\\ColumnSelect';
    public function __construct(WDB\Structure\ForeignKeyData $key, iTable $table, array $analyzerColumns)
    {
        $this->key = $key;
        $this->analyzerColumns = array();
        $this->nullable = true;
        foreach ($this->key->columns as $columnName)
        {
            $this->analyzerColumns[$columnName] = $analyzerColumns[$columnName];
            if (!$analyzerColumns[$columnName]->isNullable())
            {
                $this->nullable = false;
            }
        }
        parent::__construct($table);
        $this->fetchValidationRules($this->rules);
        
    }
    
    /**@var WDB\Analyzer\iColumn[]*/
    protected $analyzerColumns;
    /**@var WDB\Validation\ColumnRules*/
    protected $rules;    
    /**@var iTable $table*/
    protected $table;
    /**@var WDB\Structure\ForeignKeyData*/
    protected $key;
    /**@var bool*/
    protected $nullable;
    /**@var array*/
    protected $optionCache = NULL;
    
    public function setFieldValue($value)
    {
        if (is_string($value))
        {
             return WDB\Utils\Strings::unserializeCommaSeparatedList($value, $this->key->columns);
        }
        elseif (is_array($value) || is_null($value))
        {
            return $value;
        }
        else
        {
            throw new Exception\BadArgument("Only string or array is accepted as foreign field value");
        }
    }
    
    public function createField($isNew, $value, WDB\Query\SelectedRow $row = NULL)
    {
        if ($row !== NULL)
        {
            $value = array();
            foreach ($this->key->columns as $column)
            {
                $value[$column] = $row[$column];
            }
        }
        return new Field($this, $isNew, $this->setFieldValue($value));
    }  
    
    protected function firstColumnAnalyzer()
    {
        return $this->analyzerColumns[$this->key->columns[0]];
    }
    
    protected function fetchValidationRules(WDB\Validation\ColumnRules $rules)
    {
        foreach ($this->analyzerColumns as $ac)
        {
            if (isset($ac->annotations->required) || !$this->isNullable())
            {
                $rules->required();
                break;
            }
        }
    }
    
    // <editor-fold defaultstate="collapsed" desc="iDBColumn overrides">
    public function getDefault()
    {
        $values = array();
        foreach ($this->analyzerColumns as $column)
        {
            $values[$column->name] = $column->default;
        }
        return $values;
    }
    
    public  function getName()
    {
        return $this->key->name;
    }
    public  function getTitle()
    {
        /**@TODO*/
        return $this->firstColumnAnalyzer()->title;
    }
    
    public function isNullable()
    {
        return $this->nullable;
    }
    
    public function rule($rule, $message = NULL, $argument = NULL)
    {
        $this->rules->set($rule, $message, $argument);
        return $this;
    }
    
    public function getWebUIClass()
    {
        return '\\WDB\\WebUI\\ColumnSelect';
    }
    public function getWebUIDisplayMode($original = NULL)
    {
        return $this->firstColumnAnalyzer()->getWebUIDisplayMode($original);
    }
    public function valueToDatatype($value)
    {
        if (is_string($value))
        {
            return WDB\Utils\Strings::unserializeCommaSeparatedList($value, $this->key->columns);
        }
        elseif (is_array($value))
        {
            return $value;
        }
        else
        {
            throw new Exception\BadArgument("only comma-separated string list or array is accepted");
        }
    }

    public function fetchReadFields(&$fields)
    {
        foreach ($this->analyzerColumns as $column)
        {
            $fields[] = new Element\SelectField($column->identifier);
        }
    }
    
    public function saveToRow(array &$row, $value)
    {
        if ($value === NULL)
        {
            foreach ($this->key->columns as $key)
            {
                $row[$key] = NULL;
            }
        }
        else
        {
            foreach ($value as $key=>$val)
            {
                $row[$key] = $val;
            }
        }
    }
    
    public function getFieldValue($value)
    {
        if ($value !== NULL)
        {
            $targetTable = WDB\Wrapper\TableFactory::fromName($this->key->refTable, 
                    $this->key->refSchema,
                    $this->table->getDatabase());
            $records = $targetTable->elevateRecords($targetTable->datasource
                    ->setFields(Element\ColumnIdentifier::ALL_COLUMNS) //not yet supported by iDatasource, fix later
                    ->filter(is_array($value) ? $value : $this->table->unserializeKey($value, $this->key->columns))
                    ->run($this->table->database));
            if (count($records) > 0)
            {
                return $records[0];
            }
            else
            {
                return NULL;
            }
            
        }
    }
    
    public function getStringValue($value)
    {
        return $value === NULL ? "=NULL" : preg_replace('~^=NULL~', '==NULL', $this->table->serializeKey($value));
    }
    
    public function isOrderable()
    {
        return TRUE;
    }
    
    public function sortDatasource(WDB\iDatasource $datasource, $asc = TRUE, $prepend = TRUE)
    {
        if (!$datasource instanceof WDB\Query\Select)
        {
            throw new Exception\BadArgument('Foreign column can be sorted only on a Select datasource type');
        }
        $condition = Element\LogicOperator::lAnd();
        $clist = array_values($this->key->columns);
        $refClist = array_values($this->key->refColumns);
        reset($clist);reset($refClist);
        do
        {
            $col = current($clist);
            $refCol = current($refClist);
            $condition->addExpression(Element\Compare::Equals(
                    Element\ColumnIdentifier::create($col, $this->key->table, $this->key->schema),
                    Element\ColumnIdentifier::create($refCol, $this->key->refTable, $this->key->refSchema)
            ));
            next($refClist);
        }
        while (next($clist));
        $datasource->setTable(
            new Element\Join(
                $datasource->getTable(),
                new Element\TableIdentifier($this->key->refTable, $this->key->refSchema),
                $condition,
                Element\Join::LEFT
            )
        );
        
        $datasource->sort(Element\ColumnIdentifier::create($this->getRefNameColumn(), $this->key->refTable, $this->key->refSchema), $asc, $prepend);
    }
    // </editor-fold>
    
    protected function getTargetTable()
    {
        return $this->table
                ->getDatabase()
                ->getTable(new Element\TableIdentifier($this->key->refTable, $this->key->refSchema));
    }
    
    protected function getRefNameColumn()
    {
        $tableAnalyzer = $this->getTargetTable();
        $aColumns = $tableAnalyzer->getColumns();
        if (isset($tableAnalyzer->annotations->nameColumn))
        {
            return $tableAnalyzer->annotations->last('nameColumn')->getText();
        }
        elseif (isset($aColumns[WDB\Config::read('recordNameColumn')]))
        {
            return WDB\Config::read('recordNameColumn');
        }
        else
        {
            throw new WDB\Exception\ConfigInsufficient("Table {$this->key->refTable} has not defined record name column");
        }
    }
    public function isFilterable()
    {
        return TRUE;
    }
    public function filterDatasource(WDB\iDatasource $datasource, $value)
    {
        $filter = array();
        $value = $this->setFieldValue($value);
        foreach ($this->analyzerColumns as $column)
        {
            $filter[] = WDB\Structure\FilterRule::create($column->getIdentifier(), $value === NULL ? NULL : $value[$column->getName()]);
        }
        $datasource->filter($filter);
    }
    
    public function getFilterOptions()
    {
        return $this->getOptions();
    }
    
    // <editor-fold defaultstate="collapsed" desc="WDB\iSelectOptions implementation">
    public function getOptions()
    {
        if ($this->optionCache === NULL)
        {
            $targetTable = WDB\Wrapper\TableFactory::fromName($this->key->refTable, $this->key->refSchema, $this->table->database);
            $tableAnalyzer = $this->getTargetTable();
            
            $nameColumn = $this->getRefNameColumn();
            $options = $targetTable->datasource->select($this->key->refColumns, $nameColumn)->run();
            $this->optionCache = array();
            foreach ($options as $option)
            {
                $value = $option[$nameColumn];
                $keyData = array();
                foreach ($this->key->refColumns as $c)
                {
                    $keyData[] = $option[$c];
                }
                $key = $this->table->serializeKey($keyData);
                $this->optionCache[$key] = $value;
            }
        }
        return $this->optionCache;
    }
    // </editor-fold>
}